﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.IO;
using System.Text;
using System.Threading;
using Common.Utilities;
using Common.Utilities.Dispatcher;
using PdfLib;
using Workflows.Components.TextMining.PdfBookmarkExtraction;

namespace Workflows.Components.TextMining.TextScraping
{
    /// <summary>
    /// 
    /// </summary>
    public class PdfSectionFilterWorker:IWorker 
    {
        #region fields
        private Thread _WorkerThread;
        private PdfSectionFilterInstruction _Instruction;
        private PdfSectionFilterOutput _Output;
        private Guid _JobTicketID;
        private RunContext _Context;
        private string _WorkflowName;
        private string _ComponentName;

        /// <summary>
        /// 
        /// </summary>
        public string ComponentName
        {
            get { return _ComponentName; }
        }

        /// <summary>
        /// 
        /// </summary>
        public string WorkflowName
        {
            get { return _WorkflowName; }
        }

        #endregion

        #region IWorker Members

        public Thread WorkerThread
        {
            get { return _WorkerThread; }
        }

        public InstructionBase Instruction
        {
            get { return _Instruction; }
        }

        public ResultBase Output
        {
            get { return _Output; }
        }

        public Type ComponentType
        {
            get { return typeof(PdfSectionFilterComponent); }
        }

        public RunContext Context
        {
            get { return _Context; }
            set { _Context = value; }
        }

        public void SetInstruction(Guid ticketGuid, InstructionBase request, string workflowName, string componentName)
        {
            this._JobTicketID = ticketGuid;
            this._Instruction = (PdfSectionFilterInstruction)request;
            this._WorkflowName = workflowName;
            this._ComponentName = componentName;
        }

        public void SetInstruction(RunContext context, Guid ticketGuid, InstructionBase request, string workflowName, string componentName)
        {
            this._JobTicketID = ticketGuid;
            this._Instruction = (PdfSectionFilterInstruction)request;
            this._Context = context;
            this._WorkflowName = workflowName;
            this._ComponentName = componentName;
        }

        public void DoWork()
        {
            if (this.WorkerStartedEvent != null)
            {
                this.WorkerStartedEvent(this._JobTicketID, this, this.GetType(), this._Instruction);
            }
            this._WorkerThread = Thread.CurrentThread;
            this.ExtractSectionContent();
        }


        public void DoWorkAsync()
        {
            if (this.WorkerStartedEvent != null)
            {
                this.WorkerStartedEvent(this._JobTicketID, this, this.GetType(), this._Instruction);
            }
            this._WorkerThread = new Thread(new ThreadStart(this.ExtractSectionContent));
            this._WorkerThread.Start();
        }

        public void CancelWork()
        {
            if (this._WorkerThread != null && this._WorkerThread.IsAlive)
            {
                this._WorkerThread.Abort();
                if (this.WorkerCancelledEvent != null)
                {
                    this.WorkerCancelledEvent(this._JobTicketID, this.GetType(), this._Instruction);
                }
            }

        }

        public event WorkerStarted WorkerStartedEvent;
        public event WorkerFinished WorkerFinishedEvent;
        public event WorkerHasError WorkerErredEvent;
        public event WorkerCancelled WorkerCancelledEvent;
        public event WorkerStatusChanged WorkerStatusChangedEvent;

        #endregion

        #region work
        private void ExtractSectionContent()
        {
            SqlConnection srcConn = new SqlConnection(this._Instruction.ConnStr);
            SqlConnection tgtConn = new SqlConnection(this._Instruction.TgtConnStr);
            try
            {
                int fileProcessed = 0;
                int sectionExtracted = 0;
                Dictionary<string, SqlParameter> parameters = new Dictionary<string, SqlParameter>();
                List<TableColumn> tgtCols = new List<TableColumn>();
                TableColumn[] cols = DbUtil.GetTableColumns(DataProviderType.MSSQL, this._Instruction.TgtConnStr,
                                                            string.Empty, this._Instruction.TgtTableName);
                if (cols != null && cols.Length > 0)
                {
                    foreach (TableColumn col in cols)
                    {
                        if (col.ColumnName.ToLower() == this._Instruction.TgtIDField.ToLower())
                        {
                            tgtCols.Add(col);
                            parameters.Add(this._Instruction.TgtIDField, new SqlParameter("@" + col.ColumnName, DBNull.Value));
                        }
                        else if (col.ColumnName.ToLower() == this._Instruction.TgtContentField.ToLower())
                        {
                            tgtCols.Add(col);
                            parameters.Add(this._Instruction.TgtContentField, new SqlParameter("@" + col.ColumnName, DBNull.Value));
                        }
                        else if (col.ColumnName.ToLower() == this._Instruction.FlagField.ToLower())
                        {
                            tgtCols.Add(col);
                            parameters.Add(this._Instruction.FlagField, new SqlParameter("@" + col.ColumnName, DBNull.Value));
                        }
                    }
                }
                string spName = "bulkInsert_" + this._Instruction.TgtTableName + "_SP";
                string spBody = StoredProcUtil.CreateStoredProcedureStatementForInsert(DataProviderType.MSSQL, spName,
                                                                                       string.Empty,
                                                                                       this._Instruction.TgtTableName,
                                                                                       tgtCols.ToArray());
                DbUtil.NewStoredProc(this._Instruction.TgtConnStr, spName, spBody);
                int totalSrcRecordCount = DbUtil.GetTableRecordCount(DataProviderType.MSSQL, this._Instruction.ConnStr,
                                                                     this._Instruction.SelectSql);

                srcConn.Open();
                SqlCommand srcCmd = srcConn.CreateCommand();
                srcCmd.CommandType = CommandType.Text;
                srcCmd.CommandText = this._Instruction.SelectSql;

                tgtConn.Open();
                SqlCommand tgtCmd = tgtConn.CreateCommand();
                tgtCmd.CommandType = CommandType.Text;
                tgtCmd.CommandText = string.Format("select {0} from {1} where {2}={3}",
                                                   this._Instruction.TgtIDField,
                                                   this._Instruction.TgtTableName,
                                                   this._Instruction.FlagField,
                                                   (int) BookmarkExtractionFlag.Successful);
                SqlDataReader tgtReader = tgtCmd.ExecuteReader();
                List<string> existingIDs=new List<string>();
                if (tgtReader != null)
                {
                    while (tgtReader.Read())
                    {
                        if (tgtReader[0] != null && tgtReader[0] != DBNull.Value)
                        {
                            existingIDs.Add(tgtReader[0].ToString());
                        }
                    }
                    tgtReader.Close();
                }
                
                tgtCmd.CommandType = CommandType.StoredProcedure;
                tgtCmd.CommandText = spName;

                SqlDataReader srcReader = srcCmd.ExecuteReader();
                if (srcReader != null)
                {
                    while (srcReader.Read())
                    {
                        if(srcReader[this._Instruction.IDField]!=null && srcReader[this._Instruction.FilePathField]!=null)
                        {
                            object srcID = srcReader[this._Instruction.IDField];
                            if(existingIDs.Contains(srcID.ToString()))
                            {
                                fileProcessed++;

                                if(this.WorkerStatusChangedEvent !=null && fileProcessed % 10==0)
                                {
                                    string msg = string.Format("Processing {0} of {1} files...", fileProcessed,
                                                               totalSrcRecordCount);
                                    int pct = fileProcessed*100/totalSrcRecordCount;
                                    this.WorkerStatusChangedEvent(this._JobTicketID, this.GetType(), this._Instruction,
                                                                 this.WorkflowName, this.ComponentName, msg, pct);
                                }
                            }
                        }
                        else
                        {
                            foreach(string key in parameters.Keys)
                            {
                                parameters[key].Value = DBNull.Value;
                            }
                            BookmarkExtractionFlag flag = BookmarkExtractionFlag.Unknown;
                            string filePath = srcReader[this._Instruction.FilePathField].ToString();
                            if(string.IsNullOrEmpty(filePath) || !File.Exists(filePath))
                            {
                                flag = BookmarkExtractionFlag.FileError;
                            }
                            else
                            {
                                try
                                {
                                    string sectionContent = 
                                        this.ExtractSectionContent(filePath, this._Instruction.HeaderPatterns,
                                        this._Instruction.FooterPatterns,this._Instruction.AcrossMultiPages,
                                        ref flag);
                                    parameters[this._Instruction.TgtContentField].Value = sectionContent;
                                    if(!string.IsNullOrEmpty(sectionContent))
                                    {
                                        sectionExtracted++;
                                    }
                                }
                                catch (Exception ex1)
                                {
                                    if(this.WorkerErredEvent !=null)
                                    {
                                        this.WorkerErredEvent(this._JobTicketID, this.GetType(), this._Instruction,
                                                              ex1.ToString(), false);
                                    }
                                }
                            }

                            parameters[this._Instruction.FlagField].Value = (int)flag;
                            tgtCmd.Parameters.Clear();
                            foreach(string key in parameters.Keys)
                            {
                                tgtCmd.Parameters.Add(parameters[key]);
                            }
                            tgtCmd.ExecuteNonQuery();
                            fileProcessed++;

                            if(this.WorkerStatusChangedEvent !=null)
                            {
                                string msg = string.Format("Processing {0} of {1} files, {2} sections extracted",
                                                           fileProcessed, totalSrcRecordCount, sectionExtracted);
                                int pct = fileProcessed*100/totalSrcRecordCount;
                                this.WorkerStatusChangedEvent(this._JobTicketID, this.GetType(), this._Instruction, this.WorkflowName, this.ComponentName, msg,
                                                              pct);
                            }
                        }
                    }
                    srcReader.Close();
                }
                this._Output=new PdfSectionFilterOutput(this._Instruction.OwnerComponentID,this._JobTicketID,fileProcessed,sectionExtracted);
                if(this.WorkerFinishedEvent !=null)
                {
                    this.WorkerFinishedEvent(this._JobTicketID, this.GetType(), this._Instruction, this._Output);
                }
            }
            catch (Exception ex)
            {
                if(this.WorkerErredEvent !=null)
                {
                    this.WorkerErredEvent(this._JobTicketID, this.GetType(), this._Instruction, ex.ToString(), true);
                }
            }
        }

        private string ExtractSectionContent(string filePath, List<string> headerPatterns, List<string> footerPatterns, bool acrossMultiPages, ref BookmarkExtractionFlag flag)
        {
            int pageCount = PdfPropertyReader.GetPageCount(filePath);
            bool startRead = false;
            bool stopRead = false;
            int startPageNum = 0;
            StringBuilder buffer=new StringBuilder();
            for(int pageNum=1; pageNum <= pageCount; pageNum ++)
            {
                if(!startRead)
                {
                    List<PdfTextLine> lines = PdfTextReader.ReadPageContentAsLines(filePath, pageNum, 8.0);
                    lines.Sort();
                    for (int i = 0; i < lines.Count; i++)
                    {
                        string matchedPattern = string.Empty;
                        double matchScore = StringUtil.FuzzyStringMatch(lines[i].LineContent, headerPatterns.ToArray(),
                                                                        ref matchedPattern, false);
                        if (matchScore >= 0.95)
                        {
                            startRead = true;
                            startPageNum = pageNum;
                            buffer.AppendLine(lines[i].LineContent);
                        }
                    }
                }
                else
                {
                    List<PdfTextLine> lines = PdfTextReader.ReadPageContentAsLines(filePath, pageNum, 8.0);
                    lines.Sort();
                    if (pageNum == startPageNum)
                    {
                        for (int i = 0; i < lines.Count; i++)
                        {
                            string matchedPattern = string.Empty;
                            double matchScore =
                                StringUtil.FuzzyStringMatch(
                                    lines[i].LineContent,
                                    footerPatterns.ToArray(),
                                    ref matchedPattern, false);
                            if (matchScore >= 0.95)
                            {
                                buffer.AppendLine(lines[i].LineContent);
                                stopRead = true;
                                break;
                            }
                        }
                    }
                    else if (pageNum > startPageNum && acrossMultiPages)
                    {
                        for (int i = 0; i < lines.Count; i++)
                        {
                            string matchedPattern = string.Empty;
                            double matchScore =
                                StringUtil.FuzzyStringMatch(
                                    lines[i].LineContent,
                                    footerPatterns.ToArray(),
                                    ref matchedPattern, false);
                            if (matchScore >= 0.95)
                            {
                                buffer.AppendLine(lines[i].LineContent);
                                stopRead = true;
                                break;
                            }
                        }
                    }
                }

                if(stopRead)
                    break;
            }

            if(buffer.Length>0)
                flag = BookmarkExtractionFlag.Successful;
            return buffer.ToString();
        }

        #endregion
    }
}
